using System;
using System.Collections.Generic;
using ICSharpCode.NRefactory;
using ICSharpCode.NRefactory.Ast;

namespace NTrace
{
  public class FileProcessor
  {
    private Environment environment;
    private CompilationUnit parsedCode;

    public FileProcessor(Environment environment)
    {
      this.environment = environment;
    }

    public String Process(String codeFile)
    {
      SupportedLanguage language;
      String fileExtension = System.IO.Path.GetExtension(codeFile);
      if (String.Compare(".cs", fileExtension, true) == 0)
      {
        language = SupportedLanguage.CSharp;
      }
      else
      {
        throw new PreprocessorException(600, String.Format("Invalid file extension '{0}'.", fileExtension));
      }

      this.EnsureOutputFolderExists();

      String outputFile = this.GetUniqueFileName(codeFile);

      PreprocessorContext context = new PreprocessorContext(this.environment, codeFile, language);

      // Since we want to preserve the original formatting from the
      // source, we'll copy the contents of the file and have our
      // processor munge it a bit to generate the trace calls
      using (System.IO.StreamReader reader = new System.IO.StreamReader(codeFile))
      {
        for (String line = reader.ReadLine(); line != null; line = reader.ReadLine())
        {
          context.Lines.Add(line);
        }
      }

      using (IParser parser = ParserFactory.CreateParser(codeFile))
      {
        // We want the contents of the methods parsed as well
        parser.ParseMethodBodies = true;
        parser.Parse();

        this.parsedCode = parser.CompilationUnit;

        NTraceAstVisitor ntraceVisitor = new NTraceAstVisitor(context);
        this.parsedCode.AcceptVisitor(ntraceVisitor, null);

        this.WriteProcessedFile(context, outputFile);
        context.TraceMessageFile.Write();
      }

      return outputFile;
    }

    /// <summary>
    /// Ensure our preprocessor's output folder exists
    /// </summary>
    private void EnsureOutputFolderExists()
    {
      if (!System.IO.Directory.Exists(this.environment.OutputFolder))
      {
        System.IO.Directory.CreateDirectory(this.environment.OutputFolder);
      }
    }

    private void WriteProcessedFile(PreprocessorContext context, String outputFile)
    {
      ICSharpCode.NRefactory.PrettyPrinter.IOutputAstVisitor printer = context.Language.CreatePrinter();
      
      // Use the printer to generate a comment header stating this is an
      // auto-generated file 
      this.AddHeader(printer);

      using (System.IO.TextWriter writer = new System.IO.StreamWriter(outputFile, false))
      {
        writer.Write(printer.Text);

        // Emit our munged source, wrapping it with processing directives that map the new lines
        // to the original source file
        using (OriginalSourceWrapper wrapper = context.Language.CreateSourceWrapper(context.CodeFile, writer))
        {
          foreach (String line in context.Lines)
          {
            writer.WriteLine(line);
          }
        }
      }
    }

    private void AddHeader(ICSharpCode.NRefactory.PrettyPrinter.IOutputAstVisitor printer)
    {
      System.Reflection.AssemblyName currentAssemblyName = System.Reflection.Assembly.GetExecutingAssembly().GetName();

      this.PrintComment(printer, String.Empty);
      this.PrintComment(printer, "Warning: this code was auto-generated by a tool.");
      this.PrintComment(printer, "Do not edit this file, as your changes will be overwritten.");
      this.PrintComment(printer, String.Empty);
      this.PrintComment(printer, String.Format("{0}, Version {1}", currentAssemblyName.Name, currentAssemblyName.Version));
      this.PrintComment(printer, String.Empty);
    }

    private void PrintComment(ICSharpCode.NRefactory.PrettyPrinter.IOutputAstVisitor printer, String text)
    {
      Comment comment = new Comment(
        CommentType.SingleLine,
        text,
        Location.Empty,
        Location.Empty);
      printer.OutputFormatter.PrintComment(comment, false);
    }

    private String GetUniqueFileName(String inputFile)
    {
      String baseFileName = System.IO.Path.GetFileNameWithoutExtension(inputFile);
      String extension = System.IO.Path.GetExtension(inputFile);
      String output = System.IO.Path.Combine(
        this.environment.OutputFolder,
        baseFileName + extension);

      int index = 0;
      while (System.IO.File.Exists(output))
      {
        String newName = String.Concat(baseFileName, index, extension);
        output = System.IO.Path.Combine(
          this.environment.OutputFolder,
          newName);
        index++;
      }

      return output;
    }
  }
}
